package br.cin.ufpe.feedbacktest.core;

import java.util.HashSet;
import java.util.Vector;

public class Sequence {

	private Vector<Call> callList;
	private Vector<Boolean> extensibleFlags;

	public Sequence() {
		this.callList = new Vector<Call>();
		this.extensibleFlags = new Vector<Boolean>();
	}

	public void addSequences(Sequence[] sequences) {
		for (int i = 0; i < sequences.length; i++) {
			Sequence sequence = sequences[i];

			for (int j = 0; j < sequence.callList.size(); j++) {
				this.callList.add(sequence.callList.get(j));
				//this.extensibleFlags.add(sequence.extensibleFlags.get(j));
				this.extensibleFlags.add(true);
			}
		}
	}

	public void addCall(Call call) {
		this.callList.add(call);
		this.extensibleFlags.add(true);
	}

	public void checkReusableVariables(Class<?> type, Vector<Variable> reusableVariables) {
		for (int index = 0; index < this.callList.size(); index++) {
			Call call = this.callList.get(index);
			Boolean extensible = this.extensibleFlags.get(index);

			if (call.getReturnType().equals(type) && extensible) {
				//Variable variable = new Variable();
				//variable.setName(call.getReturnVariable().getName());
				//variable.setType(call.getReturnVariable().getType());
				//variable.setValue(call.getReturnVariable().getValue());

				reusableVariables.add(call.getReturnVariable());
			}
		}
	}

	public boolean execute() {
		// Flag to indicate violation
		// in the sequence execution
		// (Return Value)
		boolean violated = false;
		
		// Cleaning
		// Object States
		for (int index = 0; index < this.callList.size(); index++) {
			Call call = this.callList.get(index);
			call.clearVariables();
		}
		
		// Executing
		// Calls
		for (int index = 0; index < this.callList.size(); index++) {
			Call call = this.callList.get(index);
			
			boolean callViolation = call.execute();
			if (callViolation) {
				violated = true;
				break;
			}

			// Checking
			// Extensibility

			if (call.isVoidMethod()) {
				continue;
			}

			Object runtimeObject = call.getReturnVariable().getValue();

			if (runtimeObject == null) {
				this.extensibleFlags.set(index, false);

			} else {
				boolean equality = false;

				for (int subindex = 0; subindex < index; subindex++) {					
					Boolean extensible = this.extensibleFlags.get(subindex);					
					if (extensible) {
						Call previousCall = this.callList.get(subindex);

						if (previousCall.isVoidMethod()) {
							continue;
						}

						Object previousRuntimeObject = previousCall.getReturnVariable().getValue();
						if (runtimeObject.equals(previousRuntimeObject)) {							
							equality = true;
							break;
						}
					}
				}

				if (equality) {
					this.extensibleFlags.set(index, false);
				}
			}
		}

		return violated;
	}
	
	public HashSet<String> getImports() {
		HashSet<String> imports = new HashSet<String>();
		
		for (int index = 0; index < this.callList.size(); index++) {
			Call call = this.callList.get(index);
			imports.addAll(call.getImports());
		}
		
		return imports;
	}

	public boolean hasSameCalls(Sequence sequence) {
		boolean has = false;

		for (Call thisCall : this.callList) {
			for (Call sequenceCall : sequence.callList) {
				if (thisCall.equals(sequenceCall)) {
					has = true;
					break;
				}
			}
			if (has) {
				break;
			}
		}

		return has;
	}

	public String getAssertCall() {
		String last = null;

		for (int index = this.callList.size() - 1; index >= 0; index--) {
			Call call = this.callList.get(index);
			if (!call.getReturnType().isPrimitive()) {
				Variable variable = call.getReturnVariable(); 
				if (variable != null) {
					last = variable.getName();
					break;
				}
			}
		}

		if (last != null) {
			return "        assertTrue(" + last + ".equals(" + last + "));\n";
		} else {
			return "";
		}
	}


	/**
	 * Equals
	 * Method.
	 */
	public boolean equals(Object object) {
		boolean equals = true;

		// If the Object
		// Class are same
		if (object instanceof Sequence) {
			Sequence sequence = (Sequence)object;

			// If they have
			// same call number
			if (this.callList.size() == sequence.callList.size()) {

				// If all the
				// calls are equals
				for (int index = 0; index < this.callList.size(); index++) {
					Call call = this.callList.get(index);
					Call sequenceCall = sequence.callList.get(index);

					if (!call.equals(sequenceCall)) {
						equals = false;
						break;
					}
				}

			} else {
				equals = false;
			}

		} else {
			equals = false;
		}

		return equals;
	}

	/**
	 * toString
	 * Method.
	 */
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		for (int index = 0; index < this.callList.size(); index++) {
			//buffer.append(index);
			//buffer.append(": ");

			Call call = this.callList.get(index);
			buffer.append(call.toString());
			buffer.append('\n');
		}
		return buffer.toString();
	}

}
